package cn.yzj.utils;

import cn.yzj.common.CommonSymbol;
import cn.yzj.entity.MinioBucketEntity;
import cn.yzj.entity.MinioFileEntity;
import cn.yzj.entity.args.MinioComposeArgs;
import cn.yzj.entity.args.MinioDownloadArgs;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.http.protocol.HTTP;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.InputStream;
import java.net.URLDecoder;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * Minio 操作工具类
 *
 * @author gzkemays
 * @since 2022/5/11 9:17
 */
public class MinioOperaUtils {
  private static MinioClient CLIENT;
  private static String END_POINT;
  public static final String MB = "MB";
  public static final String GB = "GB";
  public static final long MAX_EXPIRED = 604800L;

  /**
   * 自定义大小
   *
   * @param size 大小
   * @param format 格式
   * @return 返回长度
   */
  public static long customerSize(int size, String format) {
    if (Objects.equals(format, MB)) {
      return (long) size * 1024 * 1024;
    }
    if (Objects.equals(format, GB)) {
      return (long) size * 1024 * 1024 * 1024;
    }
    return size * 1024L;
  }
  /**
   * 初始化 MinIoClient
   *
   * @param accessKey access 密钥（账号）
   * @param secretKey secret 密钥（密码）
   * @param endPoint 连接 minio 的接口地址
   */
  public MinioOperaUtils(String accessKey, String secretKey, String endPoint) {
    CLIENT = MinioClient.builder().credentials(accessKey, secretKey).endpoint(endPoint).build();
    END_POINT = endPoint;
  }

  /**
   * 创建桶
   *
   * @param bucketName 桶名
   */
  @SneakyThrows
  public void createBucket(String bucketName) {
    if (!bucketExists(bucketName)) {
      CLIENT.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    }
  }

  /**
   * 判断 bucket 是否存在
   *
   * @param bucketName 桶名
   * @return 判断结果
   */
  @SneakyThrows
  public boolean bucketExists(String bucketName) {
    return CLIENT.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
  }

  /**
   * 获取文件 url
   *
   * @param entity file
   * @return 文件地址
   */
  @SneakyThrows
  public String getObjectUrl(MinioFileEntity entity) {
    return CLIENT.getPresignedObjectUrl(
        GetPresignedObjectUrlArgs.builder()
            .method(Method.GET)
            .bucket(entity.getBucketName())
            .object(buildPath(entity.getFolder(), entity.getFileName()))
            .expiry(expiryHandle(entity.getExpiry()))
            .build());
  }

  @SneakyThrows
  public MinioFileEntity getFileEntity(MinioFileEntity entity) {
    Iterable<Result<Item>> results =
        CLIENT.listObjects(
            ListObjectsArgs.builder()
                .bucket(entity.getBucketName())
                .prefix(buildPath(entity.getFolder(), entity.getFileName()))
                .build());
    for (Result<Item> result : results) {
      Item item = result.get();
      if (item != null) {
        entity.setFileSize(buildSize(item.size()));
        entity.setUpdateTime(localDateTime2Date(item.lastModified().toLocalDateTime()));
        entity.setAccessUrl(getObjectUrl(entity));
        break;
      }
    }
    return entity;
  }

  /**
   * 一次性上传文件
   *
   * @param entity 文件对象
   * @return 公域访问地址
   */
  @SneakyThrows
  public String uploadFile(MinioFileEntity entity) {
    String objName;
    String bucket = entity.getBucketName();
    String folder = entity.getFolder();
    MultipartFile file = entity.getFile();
    createBucket(bucket);
    // 设置 bucket 代理
    CLIENT.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).build());
    SimpleDateFormat sdf = new SimpleDateFormat(DateFormatUtils.NORMAL2_FORMAT);
    // 文件存储的结构目录
    if (Objects.isNull(folder)) {
      objName = buildPath(sdf.format(new Date()), file.getOriginalFilename());
    } else {
      objName = buildPath(folder, file.getOriginalFilename());
    }
    CLIENT.putObject(
        PutObjectArgs.builder().bucket(bucket).object(objName).stream(
                file.getInputStream(), file.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)
            .contentType(file.getContentType())
            .build());
    return END_POINT
        + CommonSymbol.LEFT_DIAGONAL_BAR
        + bucket
        + CommonSymbol.LEFT_DIAGONAL_BAR
        + objName;
  }

  /**
   * 删除指定 bucket 中的 path 下的文件
   *
   * @param fileEntity file
   */
  @SneakyThrows
  public void deleteFile(MinioFileEntity fileEntity) {
    CLIENT.removeObject(
        RemoveObjectArgs.builder()
            .bucket(fileEntity.getBucketName())
            .object(buildPath(fileEntity.getFolder(), fileEntity.getFileName()))
            .build());
  }

  @SneakyThrows
  public MinioBucketEntity getBucketInfo(String bucket) {
    Bucket minioBucket =
        CLIENT.listBuckets().stream()
            .filter(b -> b.name().equals(bucket))
            .findFirst()
            .orElseThrow(() -> new RuntimeException("没有指定桶"));
    MinioBucketEntity minioBucketEntity = new MinioBucketEntity();
    minioBucketEntity.setName(minioBucket.name());
    minioBucketEntity.setCreateTime(
        localDateTime2Date(minioBucket.creationDate().toLocalDateTime()));
    Iterable<Result<Item>> results =
        CLIENT.listObjects(ListObjectsArgs.builder().bucket(bucket).build());
    long filesSize = 0L;
    for (Result<Item> result : results) {
      Item item = result.get();
      filesSize += item.size();
    }
    minioBucketEntity.setSize(filesSize);
    return minioBucketEntity;
  }

  @SneakyThrows
  public List<MinioFileEntity> getBucketFileList(String bucket) {
    return resultsParseToEntities(
        CLIENT.listObjects(ListObjectsArgs.builder().bucket(bucket).build()), null);
  }

  @SneakyThrows
  public List<MinioFileEntity> getBucketFileList(String bucket, boolean buildSize) {
    return resultsParseToEntities(
        CLIENT.listObjects(ListObjectsArgs.builder().bucket(bucket).build()), null, buildSize);
    /*    for (Result<Item> item : results) {
      MinioFileEntity build =
          MinioFileEntity.builder()
              .fileName(URLDecoder.decode(item.get().objectName(), "UTF-8"))
              .updateTime(localDateTime2Date(item.get().lastModified().toLocalDateTime()))
              .build();
      long size = item.get().size();
      build.setFileSize(buildSize(size));
      entities.add(build);
    }*/
  }
  /**
   * 获取全部 bucket 的文件
   *
   * @return 文件列表
   */
  @SneakyThrows
  public List<MinioFileEntity> getAllBucketFileList() {
    List<Bucket> buckets = CLIENT.listBuckets();
    List<MinioFileEntity> fileVos = new ArrayList<>();
    if (!buckets.isEmpty()) {
      for (Bucket bucket : buckets) {
        Iterable<Result<Item>> results =
            CLIENT.listObjects(ListObjectsArgs.builder().bucket(bucket.name()).build());
        fileVos.addAll(resultsParseToEntities(results, URLDecoder.decode(bucket.name(), "UTF-8")));
      }
    }
    return fileVos;
  }

  /**
   * 断点下载
   *
   * @param args 下载参数
   * @return 返回文件流
   */
  @SneakyThrows
  public InputStream chunkDownload(MinioDownloadArgs args) {
    return CLIENT.getObject(
        GetObjectArgs.builder()
            .bucket(args.getBucket())
            .object(args.getObject())
            .offset(args.getOffset())
            .length(args.getLength())
            .build());
  }
  /**
   * 合并分片文件
   *
   * @param args 合并数据
   */
  @SneakyThrows
  public void composeChuckFile(MinioComposeArgs args) {
    Iterable<Result<Item>> results =
        CLIENT.listObjects(
            ListObjectsArgs.builder()
                .bucket(args.getChuckBucket())
                .prefix(buildPath(args.getChuckFolder(), args.getChuckPrefix()))
                .recursive(true)
                .build());
    List<ComposeSource> objs = new ArrayList<>();
    for (Result<Item> result : results) {
      if (args.isUseDecode()) {
        objs.add(
            ComposeSource.builder()
                .bucket(args.getChuckBucket())
                .object(URLDecoder.decode(result.get().objectName(), args.getCharset()))
                .build());
      } else {
        objs.add(
            ComposeSource.builder()
                .bucket(args.getChuckBucket())
                .object(result.get().objectName())
                .build());
      }
    }
    // 添加转化类型
    Map<String, String> header =
        new HashMap<String, String>(1) {
          {
            put(HTTP.CONTENT_TYPE, args.getContentType());
          }
        };
    // 开始合并文件
    CLIENT.composeObject(
        ComposeObjectArgs.builder()
            .bucket(args.getComposeBucket())
            .object(buildPath(args.getComposeFolder(), args.getComposeFileName()))
            .headers(header)
            .sources(objs)
            .build());
    // 是否删除分片信息
    if (args.isDeleteChuck()) {
      List<DeleteObject> deleteObjects = new ArrayList<>(objs.size());
      for (ComposeSource obj : objs) {
        deleteObjects.add(new DeleteObject(obj.object()));
      }
      CLIENT.removeObjects(
          RemoveObjectsArgs.builder().bucket(args.getChuckBucket()).objects(deleteObjects).build());
    }
  }

  /**
   * LocalDateTime转换为Date
   *
   * @param localDateTime 时间转换
   */
  private String localDateTime2Date(LocalDateTime localDateTime) {
    // 获取当前系统的时区 id
    ZoneId zoneId = ZoneId.systemDefault();
    ZonedDateTime zdt = localDateTime.atZone(zoneId);
    Date date = Date.from(zdt.toInstant());
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    // 由于获取的时间存在时间差，我这里手动加上8小时
    cal.add(Calendar.HOUR_OF_DAY, 8);
    date = cal.getTime();
    return DateFormatUtils.dateToStr(date, DateFormatUtils.NORMAL_FORMAT);
  }

  /**
   * 将分钟数转换为秒数
   *
   * @param expiry 过期时间(分钟数)
   * @return expiry
   */
  private int expiryHandle(Integer expiry) {
    if (expiry == null) {
      expiry = 1440;
    }
    if (expiry * 60 > MAX_EXPIRED) {
      return 604800;
    }
    return expiry;
  }

  /**
   * 构造中文大小
   *
   * @param size 大小长度
   * @return 中文描述
   */
  private String buildSize(long size) {
    DecimalFormat df = new DecimalFormat("0.00");
    if (size > (customerSize(1, GB))) {
      return df.format(((double) size / 1024 / 1024 / 1024)) + GB;
    } else if (size > customerSize(1, MB)) {
      return df.format(((double) size / 1024 / 1024)) + MB;
    } else if (size > customerSize(1, null)) {
      return df.format(((double) size / 1024)) + "KB";
    } else {
      return size + "bytes";
    }
  }

  @SneakyThrows
  private List<MinioFileEntity> resultsParseToEntities(
      Iterable<Result<Item>> results, String bucketName) {
    return resultsParseToEntities(results, bucketName, true);
  }

  @SneakyThrows
  private List<MinioFileEntity> resultsParseToEntities(
      Iterable<Result<Item>> results, String bucketName, boolean buildSize) {
    List<MinioFileEntity> entities = new ArrayList<>();
    for (Result<Item> item : results) {
      MinioFileEntity build =
          MinioFileEntity.builder()
              .fileName(URLDecoder.decode(item.get().objectName(), "UTF-8"))
              .updateTime(localDateTime2Date(item.get().lastModified().toLocalDateTime()))
              .build();
      if (bucketName != null) {
        build.setBucketName(bucketName);
      }
      long size = item.get().size();
      build.setFileSize(buildSize ? buildSize(size) : size + "");
      entities.add(build);
    }
    return entities;
  }

  private String buildPath(String folder, String fileName) {
    assert fileName != null;
    if (folder == null) {
      return fileName;
    } else {
      if (folder.lastIndexOf(File.separator) > 0) {
        return folder + fileName;
      }
      return folder + File.separator + fileName;
    }
  }
}
